package com.google.ant.extensions.types;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;

import org.w3c.dom.Document;

import com.google.ant.extensions.SourceRepo;
import com.google.ant.extensions.tasks.InstallTask;
import com.google.ant.extensions.utils.XMLUtils;
import com.google.ant.extensions.utils.XPathUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.types.DataType;

public class Module extends DataType {
    public static final String PRJ_SEP = "**";
    public static final String ITEM_SEP = "|";

    private String name;
    private String buildfile = "build.xml";
    private String basedir;

    private String signature;

    private Collection<Module> cache;

    private Long elapsed;

    private boolean builddep = true;

    private Project moduleProject;

    private String niceName;

    public void setBuilddep(boolean b) {
        this.builddep = b;
    }

    public void setElapsed(Long l) {
        this.elapsed = l;
    }

    public Long getElapsed() {
        return this.elapsed;
    }

    public void setBuildfile(String f) {
        this.buildfile = f;
    }

    public String getBuildfile() {
        return this.buildfile;
    }

    public void setName(String i) {
        this.name = i;
    }

    public String getName() {
        return this.name;
    }

    public String getNiceName() {
        return this.niceName;
    }

    public void setNiceName(String nn) {
        this.niceName = nn;
    }

    public String toString() {
        return name;
    }

    public void setBasedir(String b) {
        this.basedir = b;
    }

    public String getBasedir() {
        return this.basedir;
    }

    public void setSignature(String d) {
        this.signature = d;
    }

    public String getSignature() {
        return this.signature;
    }

    public static Module parse(String line) {
        Module m = new Module();
        StringTokenizer stoken = new StringTokenizer(line, "|");
        m.setSignature(stoken.nextToken());
        m.setBasedir(new File(stoken.nextToken()).getAbsolutePath());
        m.setBuildfile(stoken.nextToken());
        m.setName(stoken.nextToken());
        m.setNiceName(m.getName());
        return m;
    }

    public void setModuleProject(Project p) {
        this.moduleProject = p;
    }
    
    private void initModuleProject() {
        if (moduleProject == null) {
            moduleProject = new Project();
            if (!new File(this.getBasedir()).exists()) {
                log(this.getBasedir() + " is not exists, ModuleProject "
                    + getName() + " can not be initialized",
                    Project.MSG_WARN);
                return;
            }
            moduleProject.setBasedir(this.getBasedir());
            moduleProject.init();

            try {
                ProjectHelper.getProjectHelper().configureProject(moduleProject, 
                                                                  new File(this.getBasedir(), 
                                                                           this.getBuildfile()));
            } catch (Exception e) {
                log("Moduleproject:" + getName() + " configureProject failed: " + e.getMessage(),
                    Project.MSG_WARN);
            }
        }
        if (moduleProject.getName() != null) {
            setNiceName(moduleProject.getName());
        }

        if (getBasedir() == null) {
            setBasedir(moduleProject.getBaseDir().getAbsolutePath());
        }

        if (getBuildfile() == null) {
            setBuildfile(new File(moduleProject.getProperty("ant.file")).getName());
        }
    }

    public String self() {
        initModuleProject();
        
        StringBuffer sb = new StringBuffer();

        String sig = resolveSignature();
        if (sig == null) {
            return null;
        }

        setSignature(sig);

        sb.append(sig);
        sb.append(ITEM_SEP);
        sb.append(this.getBasedir());
        sb.append(ITEM_SEP);
        sb.append(this.getBuildfile());
        sb.append(ITEM_SEP);
        sb.append(this.getName());
            
        return sb.toString();
    }

    public void selfRegister() {
        if (moduleProject == null) {
            initModuleProject();
        }
        new SourceRepo(this.moduleProject).add(this);
    }

    private String resolveSignature() {
        return getSignature(moduleProject.getProperty(InstallTask.PROJECT_GROUP_ID),
                            moduleProject.getProperty(InstallTask.PROJECT_ARTIFACT_ID),
                            moduleProject.getProperty(InstallTask.PROJECT_VERSION));
    }

    private String getSignature(String gid, String aid, String version) {
        StringBuffer sb = new StringBuffer();
        if (gid == null) {
            return null;
        } else {
            sb.append(gid);
        }
        sb.append(PRJ_SEP);

        if (aid == null) {
            return null;
        } else {
            sb.append(aid);
        }
        sb.append(PRJ_SEP);

        if (version == null) {
            return null;
        } else {
            sb.append(version);
        }

        return sb.toString();
    }

    public boolean hasBuildFile() {
        return hasBuildFile(new File(getBasedir()));
    }

    private boolean hasBuildFile(File dir) {
        return new File(dir, this.buildfile).exists();
    }

    public Collection<Module> getSubModules() {
        if (null == getBasedir()) {
            throw new BuildException("Basedir of module can not be null");
        }
        File parentDir = new File(getBasedir());
        if (!parentDir.exists()) {
            throw new BuildException("module:" + getName() + " is not exist");
        }

        List<Module> subs = new ArrayList<Module>();

        File[] subDirs = parentDir.listFiles(new FileFilter() {
                public boolean accept(File pathname) {
                    return pathname.isDirectory() && hasBuildFile(pathname);
                }
            });
        
        for (File subdir : subDirs) {
            Module module = new Module();
            module.setBasedir(subdir.toString());
            module.setName(parentDir.getName() + File.separator + subdir.getName());
            subs.add(module);
        }
        return subs;
    }

    public Collection<Module> getDependencies() {
        if (!builddep) {
            return new ArrayList<Module>();
        }
        if (cache != null) {
            return cache;
        }

        cache = new ArrayList<Module>();
        File dep = new File(basedir, "etc/ant-inc/dependencies.xml");
        if (!dep.exists()) {
            return new ArrayList<Module>();
        }
        List<String> deps = new ArrayList<String>();

        try {
            Document doc = XMLUtils.parse(dep);
            XPathUtils xpath = new XPathUtils();
            List<String> groupids = xpath.getStringList("//dependency/@groupid", doc);
            List<String> artifactids = xpath.getStringList("//dependency/@artifactid", doc);
            List<String> versions = xpath.getStringList("//dependency/@version", doc);

            for (int i = 0; i < groupids.size(); i++) {
                String sig = getSignature(groupids.get(i), artifactids.get(i), versions.get(i));
                if (sig != null) {
                    deps.add(sig);
                }
            }
        } catch (Exception e) {
            getProject().log(e.getMessage(), Project.MSG_WARN);
        }

        Collection<Module> cachedModules = new SourceRepo(getProject()).get();
        for (Module m : cachedModules) {
            if (deps.contains(m.getSignature())) {
                cache.add(m);
            }
        }
        return cache;
    }

    public String getHash() {
        StringBuffer sb = new StringBuffer();
        //sb.append(name);
        sb.append(basedir);
        sb.append(buildfile);
        return sb.toString();
    }

    public int hashCode() {
        return getHash().hashCode();
    }

    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (o instanceof Module) {
            Module m = (Module)o;
            return m.getHash().equals(getHash());
        } else {
            return false;
        }
    }
}
